﻿using JunionRobotTaskManager.Extensions;
using JunionRobotTaskManager.Models;
using JunionRobotTaskManager.Models.ElevatorControl;
using JunionRobotTaskManager.Repository;
using JunionRobotTaskManager.Utility;
using LazyCache;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SuperSimpleTcp;
using System.Text;
using System.Text.RegularExpressions;

namespace JunionRobotTaskManager.Services
{
    /// <summary>
    /// 机器人通信的TcpServer,Robot发送Socket信息，这里进行处理
    /// </summary>
    public class TcpServerService : BackgroundService
    {


        #region Field
        private SimpleTcpServer tcpServer = null;
        private readonly ILogger<TcpServerService> _logger;
        private readonly IServiceProvider serviceProvider;
        private readonly IOptionsMonitor<SystemInfo> _options;
        private readonly ParkService _parkService;

        private readonly IAppCache _cache;
        // private readonly IEventBus _eventBus;
        private List<Robot> robots = null;
        List<byte> reply = Encoding.UTF8.GetBytes("recv").ToList();

        List<byte> errorReply = Encoding.UTF8.GetBytes("ng").ToList();
        Dictionary<string, Robot> currentLogical = new Dictionary<string, Robot>();
        private readonly static object datalock = new object();
        private Elevator _elevator;
        #endregion

        #region ctor
        public TcpServerService(ILogger<TcpServerService> logger,
             IOptionsMonitor<SystemInfo> options,
             IAppCache cache,
        //IEventBus eventBus,
        IServiceProvider serviceProvider,
        ParkService parkService)
        {
            this._options = options;
            this._logger = logger;
            this.serviceProvider = serviceProvider;
            this._cache = cache;
            // this._eventBus = eventBus;
            reply.Insert(0, (byte)reply.Count);
            _parkService = parkService;
        }
        #endregion

        #region Method


        /// <summary>
        /// 启动时运行
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            #region 后台Tcp任务

            try
            {
                tcpServer = new SimpleTcpServer($"*:{_options.CurrentValue.Port}", false, null, null);
                tcpServer.Events.ClientDisconnected += Events_ClientDisconnected;
                tcpServer.Events.ClientConnected += Events_ClientConnected;
                tcpServer.Events.DataReceived += Events_DataReceived;
                tcpServer.StartAsync();

                // hay add 20240521
                if (_options.CurrentValue.HasElevator)
                {
                    _elevator = new Elevator(_options.CurrentValue.ElevatorIP, _options.CurrentValue.ElevatorLowestFloor, _options.CurrentValue.ElevatorHightestFloor, _options.CurrentValue.ElevatorCallSpanTime);
                    _elevator.Logger += _logger.LogInformation;
                    _options.OnChange((setting, _) => _elevator.CallSpanTime = setting.ElevatorCallSpanTime);
                    _elevator.Connect();
                    _elevator.Monitor();
                }
            }
            catch (Exception ex)
            {
                _logger.LogInformation($"Tcp Server start failed! The error info is:{ex.Message}");


            }

            return Task.Delay(500);

            #endregion
        }

        #region TcpMethod

        /// <summary>
        /// 客户端断开连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Events_ClientDisconnected(object sender, ConnectionEventArgs e)
        {
            _logger.LogInformation($"{e.IpPort} Disconnected!");
        }

        /// <summary>
        /// 客户端连接成功
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Events_ClientConnected(object sender, ConnectionEventArgs e)
        {

            _logger.LogInformation($"{e.IpPort} Connected!");

        }
        #endregion



        /// <summary>
        /// 汇报给MES
        /// </summary>
        /// <param name="isComplete"></param>
        /// <param name="robot"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        private bool ReportToMes(bool isComplete, Robot robot, string status)
        {

            string reportinfo = "";
            string url = "";
            if (isComplete)
            {
                var data = new
                {

                    Command = "Result",
                    Id = robot.CurrentJob.Id,
                    Status = status,
                    Remark = ""

                };
                url = _options.CurrentValue.CompleteUrl;
                reportinfo = JsonConvert.SerializeObject(data);
            }
            else
            {
                var data = new
                {
                    Command = "Info",
                    Id = robot.CurrentJob.Id,
                    Robot = robot.Id,
                    DockType = robot.CurrentJob.Targets[robot.CurrentJob.CurrentTarget].ToLower() == "put" ? 2 : 1,
                    Status = status,
                    Target = robot.CurrentJob.CurrentTarget,
                    Remark = ""
                };
                url = _options.CurrentValue.DeliveryUrl;
                reportinfo = JsonConvert.SerializeObject(data);
            }

            _logger.LogInformation($"The robot {robot.IP} Report the  mes data is:{reportinfo}");
            ResponseResult apiResult = new ResponseResult() { Data = "failed" };
            try
            {
#if !DEBUG
                string responseData = responseData = HttpHelper.RequestPost(url, reportinfo);
                _logger.LogInformation($"The robot {robot.IP} Receive the mes data is:{responseData}");
                apiResult = JsonConvert.DeserializeObject<ResponseResult>(responseData);
   
                if ((apiResult != null && apiResult.Data.ToLower() == "succeed"))
                {
                    return true;
                }
                else
                {
                    _logger.LogInformation($"Send to the mes data failed ");
                    return false;
                }
#else
                return true;
#endif

            }
            catch (Exception ex)
            {
                _logger.LogError($"The robot {robot.IP} Report mes message occurs error,the error info is:{ex.Message}");
                return false;
            }


        }

        #endregion


        /// <summary>
        /// 接受信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Events_DataReceived(object sender, DataReceivedEventArgs e)
        {
            TransferPort targetPort = null;
            Robot robot = null;


            #region 【1】 记录收到信息
            string msg = Encoding.UTF8.GetString(e.Data);
            _logger.LogInformation($"receive the robot ({e.IpPort}) send  message is:{msg}");
            string ip = e.IpPort.Split(":")[0];
            msg = msg.ToLower();
            #endregion

            lock (datalock)
            {

                #region 【2】读取缓存，如果有直接返回消息
                //如果已经缓存,直接返回,由于Robot是间隔性发送socket,不是长连接，所以当前连接处理成功后，下一次socket端口号会不一致了。
                bool isDone = false;
                if (_cache.TryGetValue(ip + msg, out isDone))
                {
                    if (isDone)
                    {
                        tcpServer.Send(e.IpPort, reply.ToArray());
                        _logger.LogInformation("After robot {0}  request {1} all action have dealed,the recoder info is in cache, reply the robot info is:recv",ip,msg);
                        return;
                    }
                }
                #endregion

                Regex regex = new Regex("-?[0-9]+f");
                var elevatorCommand = new string[] { "enterelevator", "closedoor", "leaveelevator", "completeelevator", "abortelevator" };
                List<TransferPort> ports = null;
                if (_cache.TryGetValue("Robots", out robots))
                {
                    #region 【3】校验机器人
                    if (!robots.Any(x => x.IP == ip))
                    {
                        _logger.LogError($"The register robots can't find the robot ip is:{ip}");
                        return;
                    }
                    robot = robots.Find(x => x.IP == ip);

                    if (!regex.Match(msg).Success && !elevatorCommand.Contains(msg))
                    {
                        if (robot.CurrentJob == null)
                        {
                            _logger.LogError($"The robot ({robot.IP}) assigned job is null,can't deal with the msg:{msg}.");
                            if (msg == "resume")
                            {
                                _logger.LogInformation("The robot{0} task has canceled,it can't be resumed!", robot.IP);
                                List<byte> errorReply = Encoding.UTF8.GetBytes("ng").ToList();
                                errorReply.Insert(0, (byte)errorReply.Count);
                                tcpServer.Send(e.IpPort, errorReply.ToArray());
                            }
                            return;
                        }
                    }
                    #endregion

                    #region 【4】确定当前对接设备
                    if (msg.Contains("+"))
                    {
                        if (robot.CurrentJob.CurrentTarget.ToLower() != msg.Split('+')[1])
                        {
                            _logger.LogInformation($"The robot ({ip}) message send target is not correct with job current,now reply robot msg is:ng ");
                            tcpServer.Send(e.IpPort, errorReply.ToArray());
                            return;
                        }

                    }

                    if (_cache.TryGetValue("Ports", out ports))
                    {
                        targetPort = ports.FirstOrDefault(x => x.Id == robot.CurrentJob.CurrentTarget);
                    }
                    if (targetPort == null && !regex.Match(msg).Success && !elevatorCommand.Contains(msg))
                    {
                        _logger.LogInformation($"The robot ({ip}) task has canceled,now reply robot msg is:ng ");
                        tcpServer.Send(e.IpPort, errorReply.ToArray());
                        return;
                    }
                    //区域校验，是否在对接口附近了
                    if (_options.CurrentValue.IsPositionCheck && !regex.Match(msg).Success && !elevatorCommand.Contains(msg))
                    {
                        if (Math.Abs(robot.PosX - targetPort.XPosition) > 1500 || Math.Abs(robot.PosY - targetPort.YPosition) > 1500)
                        {
                            _logger.LogInformation($"The robot({robot.IP},{robot.PosX},{robot.PosY}) is too far away the target {targetPort}({targetPort.XPosition},{targetPort.YPosition})");
                            return;
                        }
                    }
                    #endregion



                }

                #region【5】逻辑校验
                var job = robot.CurrentJob;

                if (msg.Contains("cancel", StringComparison.OrdinalIgnoreCase))
                {

                    if (ReportToMes(true, robot, "Aborted"))
                    {
                        using (var scope = serviceProvider.CreateScope())
                        {
                            if (job.ChangeStatus("abort").UpdateInfo(scope.ServiceProvider.GetService<IJobRepoitory>(), job.Status))
                            {
                                tcpServer.Send(e.IpPort, reply.ToArray());
                                _logger.LogInformation("After dealWith cancel  reply the robot info is:recv");
                                _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(16));
                                robot.CurrentJob = null;
                            }
                        }
                    }
                }
                else if (msg == "resume")
                {

                }
                else if (msg.Contains("arrived", StringComparison.OrdinalIgnoreCase))
                {
                    if (ReportToMes(false, robot, "Arrived"))
                    {
                        using (var scope = serviceProvider.CreateScope())
                        {
                            if (job.ChangeStatus("arrived").UpdateInfo(scope.ServiceProvider.GetService<IJobRepoitory>(), job.Status))
                            {
                                tcpServer.Send(e.IpPort, reply.ToArray());
                                _logger.LogInformation("After dealWith arrived  reply the robot info is:recv");
                                _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(16));
                            }
                        }
                    }

                }
                else if (msg.Contains("finished", StringComparison.OrdinalIgnoreCase))
                {

                    using (var scope = serviceProvider.CreateScope())
                    {
                        IJobRepoitory jobRepoitory = scope.ServiceProvider.GetService<IJobRepoitory>();
                        var temp = robot.CurrentJob.JobIndex;

                        #region 上报info信息
                        if (!ReportToMes(false, robot, "Finished"))
                        {
                            _logger.LogInformation($"Report mes the finished info error,the mes reponse error info");
                            return;
                        }
                        #endregion

                        if (robot.CurrentJob.Status != TaskState.Complete)
                            robot.CurrentJob.JobIndex++;
                        if (job.ChangeStatus("finished").UpdateInfo(jobRepoitory, job.Status))
                        {
                            _logger.LogInformation($"After finished updated, the robot({robot.Id}),status:{robot.Status} current job {(robot.CurrentJob?.Id)} JobIndex:{job.JobIndex},");

                            if (job.Status == TaskState.Complete)
                            {

                                if (!ReportToMes(true, robot, "Finished"))
                                {
                                    _logger.LogInformation($"Report mes the Complete info error,the mes reponse error info");
                                    return;
                                }
                                if (robot.CurrentJob.Type != TaskType.Get)
                                {
                                    if (_options.CurrentValue.IsQuickMatch)
                                    {

                                        _logger.LogInformation($"The robot {robot.IP} Begin search the best position match, The current PostionNumber is :({targetPort.LocationNumber}) ");

                                        var targets = ports.Where(x => x.LocationNumber == targetPort.LocationNumber).Select(x => x.Id).ToList();
                                        var newjob = jobRepoitory.Query(x => (x.Type == TaskType.Get || x.Type == TaskType.PickUp) && x.Status == TaskState.Pending).ToList().
                                                   Where(x => targets.Contains(x.Targets.Keys.First())).OrderByDescending(x => x.Priority).
                                                   ThenBy(x => x.CreateTime).FirstOrDefault();
                                        if (newjob != null)
                                        {
                                            _logger.LogInformation($"The robot {robot.IP} Best position match,now find a new job is:{newjob}");
                                            var target = targetPort = ports.FirstOrDefault(x => x.Id == newjob.CurrentTarget);
                                            if (Math.Abs(robot.PosX - targetPort.XPosition) > 2600 || Math.Abs(robot.PosY - targetPort.YPosition) > 2600)
                                            {
                                                _logger.LogInformation($"The robot({robot.IP},{robot.PosX},{robot.PosY}) is too far away the target {targetPort}({targetPort.XPosition},{targetPort.YPosition})");
                                                return;
                                            }
                                            else
                                            {
                                                _logger.LogInformation($"The robot {robot.IP} Current position find best job,now begin the best job:{newjob}");
                                                if (robot.BeginJob(newjob, scope, null, _logger, "b"))
                                                {
                                                    _logger.LogInformation($"The robot {robot.IP}  excuete new  find best job-{newjob.Id}");
                                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(16));
                                                    return;
                                                }
                                                else
                                                {
                                                    _logger.LogInformation($"The robot {robot.IP}  excuete new  find best job failed!");
                                                }

                                            }


                                        }
                                        else
                                        {
                                            _logger.LogInformation($"The robot {robot.IP} Current position {targetPort.LocationNumber} can't find  best job");
                                        }

                                    }


                                    robot.CurrentJob = null;

                                }


                            }

                            
                            _logger.LogInformation($"After dealWith finished  reply the robot {robot.IP} info is:recv");
                            _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(16));
                            tcpServer.Send(e.IpPort, reply.ToArray());

                            //回park点
                            if (_options.CurrentValue.IsParkControl)
                            {

                                _parkService.DealPark(robot);

                            }
                        }
                        else
                        {
                            robot.CurrentJob.JobIndex = temp;
                        }

                    }
                }

                // hay add 20240521
                try
                {
                    var targetFloor = targetPort?.Floor ?? 0;
                    if (_elevator.ControlStream.InExecution && _elevator.RobotId == robot.Id)
                    {
                        targetFloor = _elevator.ControlStream.TargetFloor;
                    }
                    _elevator.GetStatusAsync().Wait();
                    if (regex.Match(msg).Success)
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else
                        {
                            if (!_elevator.ControlStream.InExecution)
                            {
                                Dictionary<int, Command> steps = new Dictionary<int, Command>
                                    {
                                        { 0, Command.EnterElevator },
                                        { 1, Command.CloseDoor },
                                        { 2, Command.LeaveElevator },
                                    };
                                targetFloor = int.Parse(msg.Replace("f", string.Empty));
                                _elevator.ControlStream.Start(steps, robot.Floor, targetFloor);

                                _elevator.RobotId = robot.Id;
                                robot.InElevator = true;
                                using var scope = serviceProvider.CreateScope();
                                IRobotRepository robotRepoitory = scope.ServiceProvider.GetService<IRobotRepository>();
                                robotRepoitory.UpdateEntity(robot);
                            }
                            tcpServer.Send(e.IpPort, reply.ToArray());
                            _logger.LogInformation($"After dealWith {msg} reply the robot info is:recv");
                            _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                        }
                    }
                    else if (msg.Contains("enterelevator"))
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else
                        {
                            if (!_elevator.ControlStream.InExecution)
                            {
                                _elevator.ControlStream.StartDefault(robot.Floor, targetFloor);
                            }
                            if (_elevator.ControlStream.CurrentStep?.Number == 0)
                            {
                                if (_elevator.CurrentFloor == robot.Floor && _elevator.DoorStatus == DoorStatus.Open)
                                {
                                    _elevator.RobotId = robot.Id;
                                    robot.InElevator = true;
                                    using var scope = serviceProvider.CreateScope();
                                    IRobotRepository robotRepoitory = scope.ServiceProvider.GetService<IRobotRepository>();
                                    robotRepoitory.UpdateEntity(robot);

                                    tcpServer.Send(e.IpPort, reply.ToArray());
                                    _logger.LogInformation("After dealWith enterelevator  reply the robot info is:recv");
                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));

                                    _elevator.ControlStream.SkipForward(1);
                                }
                                else
                                {
                                    _elevator.ControlStream.ExecuteCurrentStepAsync(true).Wait();
                                }
                            }
                            else if (_elevator.ControlStream.PreviousStep?.Number == 0)
                            {
                                if (_elevator.CurrentFloor == robot.Floor && _elevator.DoorStatus == DoorStatus.Open)
                                {
                                    _elevator.RobotId = robot.Id;
                                    robot.InElevator = true;
                                    using var scope = serviceProvider.CreateScope();
                                    IRobotRepository robotRepoitory = scope.ServiceProvider.GetService<IRobotRepository>();
                                    robotRepoitory.UpdateEntity(robot);

                                    tcpServer.Send(e.IpPort, reply.ToArray());
                                    _logger.LogInformation("After dealWith enterelevator  reply the robot info is:recv");
                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                                }
                                else
                                {
                                    _elevator.ControlStream.ExecutePreviousStepAsync().Wait();
                                }
                            }
                            else
                            {
                                _logger.LogWarning($"{robot.Id} request enter elevator invalid");
                            }
                        }
                    }
                    else if (msg.Contains("closedoor"))
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else if (string.IsNullOrEmpty(_elevator.RobotId) || _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: not in elevator");
                        }
                        else
                        {
                            if (_elevator.ControlStream.CurrentStep.Number == 1)
                            {
                                if (_elevator.DoorStatus == DoorStatus.Close)
                                {
                                    tcpServer.Send(e.IpPort, reply.ToArray());
                                    _logger.LogInformation("After dealWith closedoor  reply the robot info is:recv");
                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));

                                    _elevator.ControlStream.SkipForward(2);
                                }
                                else
                                {
                                    _elevator.ControlStream.ExecuteCurrentStepAsync(true).Wait();
                                }
                            }
                            else if (_elevator.ControlStream.PreviousStep.Number == 1)
                            {
                                if (_elevator.DoorStatus == DoorStatus.Close)
                                {
                                    tcpServer.Send(e.IpPort, reply.ToArray());
                                    _logger.LogInformation("After dealWith closedoor  reply the robot info is:recv");
                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                                }
                                else
                                {
                                    _elevator.ControlStream.ExecutePreviousStepAsync().Wait();
                                }
                            }
                            else
                            {
                                _logger.LogWarning($"{robot.Id} request close door invalid");
                            }
                        }
                    }
                    else if (msg.Contains("leaveelevator"))
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else if (string.IsNullOrEmpty(_elevator.RobotId) || _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: not in elevator");
                        }
                        else
                        {
                            if (_elevator.ControlStream.CurrentStep.Number == 2)
                            {
                                if (_elevator.CurrentFloor == targetFloor && _elevator.DoorStatus == DoorStatus.Open)
                                {
                                    tcpServer.Send(e.IpPort, reply.ToArray());
                                    _logger.LogInformation("After dealWith leaveelevator  reply the robot info is:recv");
                                    _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                                }
                                else
                                {
                                    _elevator.ControlStream.ExecuteCurrentStepAsync(false).Wait();
                                }
                            }
                            else
                            {
                                _logger.LogWarning($"{robot.Id} request leave elevator invalid");
                            }
                        }
                    }
                    else if (msg.Contains("completeelevator"))
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else
                        {
                            _elevator.CloseDoorAsync().Wait();
                            if (_elevator.ControlStream.InExecution)
                            {
                                _elevator.ControlStream.Stop();

                                robot.InElevator = false;
                                robot.Floor = targetFloor;
                                using var scope = serviceProvider.CreateScope();
                                IRobotRepository robotRepoitory = scope.ServiceProvider.GetService<IRobotRepository>();
                                robotRepoitory.UpdateEntity(robot);
                            }
                            tcpServer.Send(e.IpPort, reply.ToArray());
                            _logger.LogInformation("After dealWith completeelevator  reply the robot info is:recv");
                            _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                        }
                    }
                    else if (msg.Contains("abortelevator"))
                    {
                        if (!string.IsNullOrEmpty(_elevator.RobotId) && _elevator.RobotId != robot.Id)
                        {
                            _logger.LogWarning($"robot({robot.Id}) request elevator ({_elevator.IP}) {GetType().Name} warn: robot({_elevator.RobotId}) in elevator");
                        }
                        else
                        {
                            _elevator.ControlStream.Abort();

                            robot.InElevator = false;
                            using var scope = serviceProvider.CreateScope();
                            IRobotRepository robotRepoitory = scope.ServiceProvider.GetService<IRobotRepository>();
                            robotRepoitory.UpdateEntity(robot);

                            tcpServer.Send(e.IpPort, reply.ToArray());
                            _logger.LogInformation("After dealWith abortelevator  reply the robot info is:recv");
                            _cache.Add(ip + msg, true, DateTime.Now.AddSeconds(8));
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError($"elevator command error: {ex.ToString()}");
                }
                #endregion

            }
        }

    }
}
